<?php
/*=============================================================================
|| ##################################################################
||	phpFoX Konsort
|| ##################################################################
||
||	Copyright		: (C) 2005-2007 PHPFOX Limited a Reality Fox Creation
||	Contact			: info@phpfox.com
||
||	- phpFoX and all of its source code and files are protected by Copyright Laws.
||
||	- The license for phpFoX permits you to install this software on a single domain (.com, .org, .net, etc.).
||
||	- You may also not remove this copyright screen which shows the copyright information and credits for phpFoX (RealityFOX Creations).
||
||	- phpFoX Konsort is NOT a FREE software - http://www.phpfox.com/license/
||
|| ##################################################################
=============================================================================*/

/* $File: ./include/classes/Validator.class.php, $Author:  $ */

/** ============================================================
 *  Input validator - checks user input against specified rules.
 * @package core
 * @see validator.mess.php
 * ============================================================ */

require_once PHPFOX_INCLUDE . 'settings/validator.sett.php';

 //extends Common
class Validator extends Object
{
    var $aErrors = array();

    /** Validation fields configuration
     * @var array
     */
    var $aFields = array();

    /** Validation rules
     * @var array
     */
    var $aRules = array();  // 0 - first op, 1 - second op, 2 - operation


    /** Callback function for whole form rules
     * @var array
     */
    var $aCallback = array();


    /** Constuctor.
     * @param array $aFields array of field validation schemes
     * @param array $aRules validation rules
     */
    function Validator($aFields, $aRules=array(), $aCallback=array() )
    {
        //for debug purposes
        Validator::log($aFields);
        // very important (!) for debug
        $aKeys = array('def', 'title', 'pattern', 'optional', 'minlen', 'maxlen', 'min', 'max', 'mineq', 'maxeq', 'callback', 'message', 'maxsize', 'minsize');
        foreach ($aFields as $sKey => $aScheme)
        {
            if (isset($aScheme['def']))
            {
                $aScheme = array_merge($this->_getDef($aScheme['def'], $aScheme['title']), $aScheme);
                unset($aScheme['def']);
            }

            if (array_diff(array_keys($aScheme), $aKeys))
                $this->_addError('validator.unknown_keys', '', true);

            $this->aFields[$sKey] = $aScheme;
        }
        $this->aRules    = $aRules;
        $this->aCallback = $aCallback;
    }

    /** Store Fields if any
     * @param array $aFields
     * @return array of all stored fields
     */
    function log($aFields=array())
    {
        static $aDebug = array();
        if ($aFields)
            $aDebug = $aDebug + $aFields;
        return $aDebug;
    }

    /** Returns specified value from a given array. supports one [] in names
     * @param string $sKey    name of variable
     * @param string $aValues values array (_POST)
     * @access private
     * @return string
     */
    function _getValue($sKey, &$aValues)
    {
        // vitally important for debug ( do not use isset (!) )
        $sVal = '';
        if ( preg_match('/^(.+)\[(.+)\]$/', $sKey, $aMat) )
            $sVal = $aValues[$aMat[1]][$aMat[2]];
        elseif ( preg_match('/^(.+)\[\]$/', $sKey, $aMat) )
            $sVal = isset($aValues[$aMat[1]])? $aValues[$aMat[1]] : array();
        elseif(is_array($aValues))
            $sVal = (array_key_exists($sKey, $aValues)) ? $aValues[$sKey] : "";

        return $sVal;
    }

    /** Checks whether data is valid according to rules
     *
     * @param array $aValues values to check
     * @return boolean true - values are valid, false - invalid values
     */
    function isValid($aValues)
    {
       //single fields
        foreach ($this->aFields as $sField => $aScheme)
            $this->_checkField($this->_getValue($sField, $aValues), $aScheme);

        //rules
        foreach ($this->aRules as $aRule)
            $this->_checkRule($aRule,$aValues);

        //callback
        foreach ($this->aCallback as $aCallback)
        {
            $aErr = call_user_func($aCallback[0], $aValues);
            foreach($aErr as $sErr)
                $this->aErrors[] = $sErr;
        }

        return (0 == sizeof($this->aErrors));
    }


    function _checkRule($aRule, $aValues)
    {
        $mOp1 = $this->_getValue($aRule[0], $aValues);
        $mOp2 = $this->_getValue($aRule[1], $aValues);
        $bError = false;
        switch ($aRule[2])
        {
            case '==':
                $bError = !($mOp1 == $mOp2);
            break;
            case '!=':
                $bError = !($mOp1 != $mOp2);
            break;
            case '<=':
                $bError = (($mOp1!='') && ($mOp2!='') && !($mOp1 <= $mOp2));
            break;
            case '>=':
                $bError = (($mOp1!='') && ($mOp2!='') && !($mOp1 >= $mOp2));
            break;
            case 'req':
                $bError = ($mOp1 and !$mOp2);
            break;

            case '>':
                $bError = (($mOp1!='') && ($mOp2!='') && !($mOp1 > $mOp2));
            break;
            case '<':
                $bError = (($mOp1!='') && ($mOp2!='') && !($mOp1 < $mOp2));
            break;

            case 'more':
                $bError = (sizeof($mOp1) < sizeof($mOp2));
            break;
            case 'less':
                $bError = (sizeof($mOp1) > sizeof($mOp2));
            break;

            default:
                $this->_addError('validator.unknown_rule', $aRule[2], true);
        }// switch

        if ($bError)
            $this->_addError('validator.rule.'.$aRule[2], array(
                $this->aFields[$aRule[0]]['title'],
                $this->aFields[$aRule[1]]['title'])
            );
    }

    /** Constructs javascript for client-side validation
     * @param string $sForm name of HTML form to validate
     * @param boolean $bReturn sets if returned string contains 'return' statement
     * @param string $sDivName Id of HTML div for error output. if empty js uses alert()
     * @return string javascript code for validation
     */
    function makeJS($sForm = 'this', $bReturn=true, $sDivName='jsErr')
    {
        $sCode    = 'new Array( ';
        // fields
        foreach ( $this->aFields as  $sField =>$aScheme )
        {
            $sCode .= "{'field':'".$sField."'";
            foreach ($aScheme as  $sKey => $sVal)
            {
                if ($sKey == 'callback') $sVal = $sVal[0];
                if (in_array($sKey, array('title','message')))
                    $sVal = "'".$sVal."'";
                $sCode .= ",'".$sKey."':".$sVal;
            }
            $sCode .= '},';
        }
        //rules
        $sCode = substr($sCode, 0, -1).'), new Array( ';
        foreach ($this->aRules as $aRule)
            $sCode .= sprintf("['%s','%s','%s','%s'],", $aRule[0], $aRule[1], $aRule[2],
                App::format('validator.rule.'.$aRule[2], array($this->aFields[$aRule[0]]['title'], $this->aFields[$aRule[1]]['title'])));
        $sCode = substr($sCode, 0, -1).'),';

        //calbacks
        $sCode = substr($sCode, 0, -1).', new Array( ';
        foreach ($this->aCallback as $aCallback)
            $sCode .= $aCallback[1].",";
        $sCode = substr($sCode, 0, -1).')';

        $sCode =  " validator_isValid($sForm, $sCode, '$sDivName')";
        if ($bReturn)
            $sCode  = 'return '.$sCode;
        return $sCode;
    }

    /** Make javascript with messages array.
     * @return string javascript code (with <script> tags) for messages array
     */
    function makeJsMess()
    {
        $aMess = App::getMessages('validator.field.');

        $sScript = '<script language="javascript" type="text/javascript"> var aValMes = {};';
        foreach ($aMess as $k=>$v)
        {
            $sKey = str_replace('validator.field.', '', $k);
            if ($sKey != $k)
                $sScript .= " aValMes['$sKey']  = '".App::format('validator.field.'.$sKey, array('%s0', '%s1', '%s2', '%s3', '%s4'))."';";
        }
        $sScript .= '</script>';
        return $sScript;
    }


    /** Validates single field.
     * @param mixed $mVal
     * @param array $aScheme
     * @access private
     * @returns boolean
     */
    function _checkField($mValue, $aScheme)
    {
        $bEmpty = ((is_string($mValue) and strlen($mValue)==0) or
                   (is_array($mValue)) and sizeof($mValue)==0);

        if ( isset($aScheme['optional']) and $bEmpty)
            return true;

        $aValue = is_array($mValue)? $mValue : array($mValue);
        $aErr = array();

        // check size
        if ( isset($aScheme['minsize']) and sizeof($aValue) < $aScheme['minsize'])
            $aErr['minsize'] = array($aScheme['title'], $aScheme['minsize'], sizeof($aValue));

        if ( isset($aScheme['maxsize']) and sizeof($aValue) > $aScheme['maxsize'])
            $aErr['maxsize'] = array($aScheme['title'], $aScheme['maxsize'], sizeof($aValue));

        foreach($aValue as $sVal)
        {
            if ( isset($aScheme['minlen']) and strlen($sVal) < $aScheme['minlen'])
                $aErr['minlen'] = array($aScheme['title'], $aScheme['minlen'], strlen($sVal));

            if ( isset($aScheme['maxlen']) and strlen($sVal) > $aScheme['maxlen'])
                $aErr['maxlen'] =  array($aScheme['title'], $aScheme['maxlen'], strlen($sVal));

            if ( isset($aScheme['pattern']) and !preg_match($aScheme['pattern'], $sVal) )
                $aErr['pattern'] = array($aScheme['title'], $aScheme['pattern'], $sVal );

            if ( isset($aScheme['min']) and $sVal < $aScheme['min'])
                $aErr['min'] = array($aScheme['title'], $aScheme['min'], $sVal);

            if ( isset($aScheme['max']) and $sVal > $aScheme['max'])
                $aErr['max'] = array($aScheme['title'], $aScheme['max'], $sVal);

            if ( isset($aScheme['mineq']) and $sVal <= $aScheme['mineq'])
                $aErr['mineq'] = array($aScheme['title'], $aScheme['mineq'], $sVal);

            if ( isset($aScheme['maxeq']) and $sVal >= $aScheme['maxeq'])
                $aErr['maxeq'] = array($aScheme['title'], $aScheme['maxeq'], $sVal);
            //if ( isset($aScheme['enum']) and !in_array($sVal, $aScheme['enum']))
            //    $aErr['enum'] =  array($aScheme['title'], $sVal);
            if (isset($aScheme['callback']))
            {
                $bValid = call_user_func($aScheme['callback'][1], $sVal);
                if (!$bValid)
                    $aErr['pattern'] = array($aScheme['title'], $sVal);
            }

            if ($aErr) // find firs field with error and stop checks
                break;
        }

        if (! $aErr)
            return true;

        if (isset($aScheme['message']))
            $this->_addError('validator.field.custom', $aScheme['message']);
        else
        {
            foreach($aErr as $sKey=>$aParam)
                $this->_addError('validator.field.'.$sKey, $aParam);
        }

        return false;
    }

    /** Gets default validation schemes by key. If unknown rule throws exeption
     * @access private
     * @param string $sKey
     * @return array scheme
     */
    function _getDef($sKey, $sTitle = '')
    {
        $aValidatorDefs = App::getParam('validator.defs');
		
        if (! array_key_exists($sKey, $aValidatorDefs))
            $this->_addError('validator.unknown_def', $sKey, true);

        if (App::hasMess('validator.def.'.$sKey))
            $aValidatorDefs[$sKey]['message'] = App::format('validator.def.'.$sKey, array($sTitle));

        return $aValidatorDefs[$sKey];
    }

    /** Checks if ip is valid
     * @param string $sIp
     * @return boolean result
     */
    function isIp($sIp)
    {
    	if ('0.0.0.0' == $sIp || '255.255.255.255' == $sIp)
    		return false;

    	$bValid = false;
        if (($nLong = ip2long($sIp)) !== false)
          $bValid = ($sIp == long2ip($nLong));

        return $bValid;
    }


    /** Checks if URI is valid
     * @param string $sUri
     * @return boolean result
     */
    function isUri($sUri)
    {
        $sRe = '&^(?:(ftp|http|https):)?                                     # 1. scheme
               (?://                                                         # authority start
               (?:((?:%[0-9a-f]{2}|[\-a-z0-9_.!~*\'\(\);:\&=\+\$,])+)@)?     # 2. authority-userinfo
               (?:((?:[a-z0-9](?:[\-a-z0-9]*[a-z0-9])?\.)*[a-z](?:[\-a-z0-9]*[a-z0-9])?\.?)  # 3. authority-hostname OR
               |([0-9]{1,3}(?:\.[0-9]{1,3}){3}))                             # 4. authority-ipv4
               (?::([0-9]*))?)?                                              # 5. authority-port
               ((?:/(?:%[0-9a-f]{2}|[\-a-z0-9_.!~*\'\(\):@\&=\+\$,;])+)+/?)? # 6. path
               (?:\?.*)?                                                     # 7. query
               $&xi';

        $aM = array();
        if (!preg_match($sRe, $sUri, $aM))
            return false;

        if (!empty($aM[4]) and !Validator::isIp($aM[4]))
            return false;

        return true;
    }

/*    function isDef($sValue, $sScheme)
    {
        return _checkField($sValue, $sScheme);
    }*/
}
?>